package com.boot2.core.utils;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ClassFile;
import javassist.bytecode.ConstPool;
import javassist.bytecode.MethodInfo;
import javassist.bytecode.annotation.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.reflect.MethodUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Objects;

/**
 * @author 李佳明
 * @date 2017.10.16
 */
@Slf4j
public class AnnotationUtil {

    /*
     * static { System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
     * }
     */

    /**
     * 运行时动态修改Class字节码，批量添加Class级别的注解和Method级别的注解
     *
     * @param classBytes
     * @param annotationMetaDataInfo
     * @return
     * @throws NotFoundException
     * @throws NoSuchMethodException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     * @throws CannotCompileException
     */
    public static void addAnnotaions(CtClass ctClass,
                                     AnnotationMetaDataInfo annotationMetaDataInfo)
            throws NotFoundException, NoSuchMethodException, IllegalAccessException,
            InvocationTargetException, CannotCompileException {

        // FIXIT: javaassit对同一个CtClass添加注解时，只有最后一个生效

//        String newClassName = annotationMetaDataInfo.getNewClassName();
        ClassFile classFile = ctClass.getClassFile();
        ConstPool constPool = classFile.getConstPool();

        // 添加Class级别的注解
        List<Annotation> classAnnotaions = annotationMetaDataInfo.getClassAnnotations();
        if (classAnnotaions != null) {
            for (Annotation annotation : classAnnotaions) {
                addAnnotationToCtClass(annotation, classFile, constPool);
            }
        }
        // 添加Method级别的注解
        List<AnnotationMetaDataInfo.MethodAnnotation> methodAnnotations = annotationMetaDataInfo
                .getMethodAnnotations();
        if (methodAnnotations != null) {
            for (AnnotationMetaDataInfo.MethodAnnotation methodAnnotation : methodAnnotations) {
                String methodName = methodAnnotation.getMethodName();
                List<Annotation> annotations = methodAnnotation.getAnnotations();
                for (Annotation annotation : annotations) {
                    addAnnotationToMethodInfo(methodName, annotation, classFile,
                            constPool);
                }
            }
        }

//        return ctClass.toClass();

    }

    /**
     * 运行时动态修改Class字节码，单条添加Class级别的注解
     *
     * @param canonicalClassName 添加注解后的Class名
     * @param classBytes         修改前的字节码
     * @param annotation         要添加的注解实例
     * @return
     * @throws NotFoundException
     * @throws NoSuchMethodException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     * @throws CannotCompileException
     */
    public static void addAnnotationToClass(CtClass ctClass, Annotation annotation)
            throws NotFoundException, NoSuchMethodException, IllegalAccessException,
            InvocationTargetException, CannotCompileException {

        ClassFile classFile = ctClass.getClassFile();
        ConstPool constPool = classFile.getConstPool();
        addAnnotationToCtClass(annotation, classFile, constPool);
    }
//	/**
//	 * 运行时动态修改Class字节码，单条添加Class级别的注解
//	 *
//	 * @param canonicalClassName 添加注解后的Class名
//	 * @param classBytes 修改前的字节码
//	 * @param annotation 要添加的注解实例
//	 * @return
//	 * @throws NotFoundException
//	 * @throws NoSuchMethodException
//	 * @throws IllegalAccessException
//	 * @throws InvocationTargetException
//	 * @throws CannotCompileException
//	 */
//	public static Class<?> addAnnotationToClass(String canonicalClassName,
//			byte[] classBytes, Annotation annotation)
//			throws NotFoundException, NoSuchMethodException, IllegalAccessException,
//			InvocationTargetException, CannotCompileException {
//
//		CtClass ctClass = createCtClass(canonicalClassName, classBytes);
//		ClassFile classFile = ctClass.getClassFile();
//		ConstPool constPool = classFile.getConstPool();
//		addAnnotationToCtClass(annotation, classFile, constPool);
//		Class<?> result = ctClass.toClass();
//		return result;
//	}

    /**
     * 添加注解到CtClass
     *
     * @param annotation
     * @param classFile
     * @param constPool
     * @throws NotFoundException
     * @throws NoSuchMethodException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     */
    private static void addAnnotationToCtClass(Annotation annotation, ClassFile classFile,
                                               ConstPool constPool) throws NotFoundException, NoSuchMethodException,
            IllegalAccessException, InvocationTargetException {
        AnnotationsAttribute attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
        javassist.bytecode.annotation.Annotation javaAssisitAnnotation = createJavaAssistAnnotaion(
                annotation, constPool);
        attr.addAnnotation(javaAssisitAnnotation);
        classFile.addAttribute(attr);
    }

    /**
     * 根据字节码创建CtClass
     * @param canonicalClassName
     * @param classBytes
     * @return
     * @throws NotFoundException
     */
//	private static CtClass createCtClass(String canonicalClassName, byte[] classBytes)
//			throws NotFoundException {
//		ClassPool pool = ClassPool.getDefault();
//		pool.insertClassPath(new ByteArrayClassPath(canonicalClassName, classBytes));
//		return pool.get(canonicalClassName);
//	}

    /**
     * 运行时动态修改Class字节码，单条添加Method级别的注解
     *
     * @param canonicalClassName
     * @param methodName
     * @param classBytes
     * @param annotation
     * @return
     * @throws NotFoundException
     * @throws NoSuchMethodException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     * @throws CannotCompileException
     */
    public static void addAnnotationToMethod(CtClass ctClass,
                                             String methodName, Annotation annotation)
            throws NotFoundException, NoSuchMethodException, IllegalAccessException,
            InvocationTargetException, CannotCompileException {
        ClassFile classFile = ctClass.getClassFile();
        ConstPool constPool = classFile.getConstPool();
        addAnnotationToMethodInfo(methodName, annotation, classFile, constPool);
//        Class<?> result = ctClass.toClass();
//        return result;
    }
//
//	/**
//	 * 运行时动态修改Class字节码，单条添加Method级别的注解
//	 * @param canonicalClassName
//	 * @param methodName
//	 * @param classBytes
//	 * @param annotation
//	 * @return
//	 * @throws NotFoundException
//	 * @throws NoSuchMethodException
//	 * @throws IllegalAccessException
//	 * @throws InvocationTargetException
//	 * @throws CannotCompileException
//	 */
//	public static Class<?> addAnnotationToMethod(String canonicalClassName,
//			String methodName, byte[] classBytes, Annotation annotation)
//			throws NotFoundException, NoSuchMethodException, IllegalAccessException,
//			InvocationTargetException, CannotCompileException {
//		CtClass ctClass = createCtClass(canonicalClassName, classBytes);
//		ClassFile classFile = ctClass.getClassFile();
//		ConstPool constPool = classFile.getConstPool();
//		addAnnotationToMethodInfo(methodName, annotation, classFile, constPool);
//		Class<?> result = ctClass.toClass();
//		return result;
//	}

    private static void addAnnotationToMethodInfo(String methodName,
                                                  Annotation annotation, ClassFile classFile, ConstPool constPool)
            throws NotFoundException, NoSuchMethodException, IllegalAccessException,
            InvocationTargetException {
        AnnotationsAttribute attr = new AnnotationsAttribute(constPool,
                AnnotationsAttribute.visibleTag);
        javassist.bytecode.annotation.Annotation javaAssisitAnnotation = createJavaAssistAnnotaion(
                annotation, constPool);
        attr.addAnnotation(javaAssisitAnnotation);
        MethodInfo method = classFile.getMethod(methodName);
        method.addAttribute(attr);
    }

    /**
     * 根据JDK的注解实例，创建javaassist的注解实例
     *
     * @param jdkAnnotation
     * @param constPool
     * @return
     * @throws NotFoundException
     * @throws NoSuchMethodException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     */
    public static javassist.bytecode.annotation.Annotation createJavaAssistAnnotaion(
            Annotation jdkAnnotation, ConstPool constPool) throws NotFoundException,
            NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        CtClass jdkAnnotaionCtClass = ClassPool.getDefault()
                .get(jdkAnnotation.annotationType().getCanonicalName());
        // 使用已有的jdk annotation构造javaassist annotation
        javassist.bytecode.annotation.Annotation javaAssistAnnotaion = new javassist.bytecode.annotation.Annotation(
                constPool, jdkAnnotaionCtClass);
        setJavaAssistAnnotation(javaAssistAnnotaion, jdkAnnotation, constPool);
        return javaAssistAnnotaion;
    }

    /**
     * 遍历jdk注解属性，设置到javaassist注解中
     *
     * @param javaAssistAnnotaion
     * @param jdkAnnotation
     * @param constPool
     * @throws NoSuchMethodException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     */
    private static void setJavaAssistAnnotation(
            javassist.bytecode.annotation.Annotation javaAssistAnnotaion,
            Annotation jdkAnnotation, ConstPool constPool) throws NoSuchMethodException,
            IllegalAccessException, InvocationTargetException, NotFoundException {

        Class<? extends Annotation> clazz = jdkAnnotation.annotationType();
        Method[] methods = clazz.getDeclaredMethods();

        //todo 由于注解格式内又可有注解，且注解内可以有类有枚举有数组，格式层级无穷无尽，故后续有空再改成递归方案
        for (Method method : methods) {
            String methodName = method.getName();
            log.info("Get method : {}", methodName);
            Object value = method.getDefaultValue();
            log.info("\tGet Default Value:{}", value);
            Class<?> returnType = method.getReturnType();
            log.info("\tGet Return Type:{}", returnType);
            Object actualValue = MethodUtils.invokeExactMethod(jdkAnnotation, methodName);
            if (!Objects.equals(value, actualValue)) {
                value = actualValue;
            }
            MemberValue memberValue = createMemberValue(returnType, value, constPool);
            if (memberValue != null) {
                javaAssistAnnotaion.addMemberValue(methodName, memberValue);
            }
        }
    }

    /**
     * 工程方法，根据returnType类型创建相应的MemberValue
     *
     * @param returnType
     * @param value
     * @param constPool
     * @return
     */
    private static MemberValue createMemberValue(Class<?> returnType, Object value,
                                                 ConstPool constPool) throws NotFoundException {
        MemberValue result = null;
        if (returnType.isAssignableFrom(Integer.class) || returnType.isAssignableFrom(int.class)) {
            result = new IntegerMemberValue(constPool);
            ((IntegerMemberValue) result).setValue((Integer) value);
        } else if (returnType.isAssignableFrom(String.class)) {
            result = new StringMemberValue(((String) value), constPool);
        } else if (returnType.isAssignableFrom(boolean.class) || returnType.isAssignableFrom(Boolean.class)) {
            result = new BooleanMemberValue((Boolean) value, constPool);
        }
        //todo 此可能会有bug,没空测试了
        //tofix 此可能会有bug,没空测试了
        else if (returnType.isAssignableFrom(Annotation.class)) {
            CtClass jdkAnnotaionCtClass = ClassPool.getDefault()
                    .get(value.getClass().getCanonicalName());
            // 使用已有的jdk annotation构造javaassist annotation
            javassist.bytecode.annotation.Annotation javaAssistAnnotaion = new javassist.bytecode.annotation.Annotation(
                    constPool, jdkAnnotaionCtClass);
            result = new AnnotationMemberValue(javaAssistAnnotaion, constPool);
        }
//        else if (returnType.isAssignableFrom(Arrays.class)) {
//            result = new ArrayMemberValue((Arrays) value, constPool);
//        }
//        else if (returnType.isArray()) {
//            result = new ArrayMemberValue((Arrays) value, constPool);
//        }
        else if (returnType.isAssignableFrom(byte.class) || returnType.isAssignableFrom(Byte.class)) {
            result = new ByteMemberValue((Byte) value, constPool);
        } else if (returnType.isAssignableFrom(char.class) || returnType.isAssignableFrom(Character.class)) {
            result = new CharMemberValue((char) value, constPool);
        } else if (returnType.isAssignableFrom(Class.class)) {
            result = new ClassMemberValue(value.getClass().getName(), constPool);
        } else if (returnType.isAssignableFrom(double.class) || returnType.isAssignableFrom(Double.class)) {
            result = new DoubleMemberValue((Double) value, constPool);
        } else if (returnType.isEnum()) {
            result = new EnumMemberValue(constPool);
            ((EnumMemberValue) result).setType(returnType.getName());
            ((EnumMemberValue) result).setValue(value.toString());
        } else if (returnType.isAssignableFrom(float.class) || returnType.isAssignableFrom(Float.class)) {
            result = new FloatMemberValue((Float) value, constPool);
        } else if (returnType.isAssignableFrom(long.class) || returnType.isAssignableFrom(Long.class)) {
            result = new LongMemberValue((Long) value, constPool);
        } else if (returnType.isAssignableFrom(short.class) || returnType.isAssignableFrom(Short.class)) {
            result = new ShortMemberValue((Short) value, constPool);
        }


        // TODO: 编写其他类型的
        return result;
    }

}